package name.manana.jarina;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.*;

import org.apache.log4j.Logger;

/**
 * This was copied from some internet example and edited to java1.5.
 *
 */
public final class JarResources {

	private static final Logger log = Logger.getLogger(JarResources.class);

    private Hashtable<String, Integer> htSizes = new Hashtable<String, Integer>();
    private Hashtable<String, byte[]> htJarContents = new Hashtable<String, byte[]>();

    // a jar file
    private String jarFileName;
    
    private String javaFullName;
    
    private String javaPath;

    /**
     * JAR-CONSTRUCTOR
     * Creates a JarResources. It extracts all resources from a Jar into an
     * internal hashtable, keyed by resource names.
     * 
     * @param jarFileName
     *            a jar or zip file
     */
    public JarResources(String jarFileName) {
        this.jarFileName = jarFileName;
        init();
    }
    
    
    /** JAVA-CONSTRUCTOR
     * @param javaPath
     * @param javaFullName
     */
    public JarResources(String javaPath, String javaFullName) {
        this.javaPath = javaPath;
        this.javaFullName = javaFullName;
    }

    public String getJarFileName() {
        return jarFileName;
    }

    /**
     * Extracts a jar resource as a blob.
     * 
     * @param name
     *            a resource name.
     */
    public byte[] getResource(String name) {
        return (byte[]) htJarContents.get(name);
    }

    /**
     * initializes internal hash tables with Jar file resources.
     */
    private void init() {
        try {
            // extracts just sizes only.
            ZipFile zf = new ZipFile(jarFileName);
            Enumeration<? extends ZipEntry> e = zf.entries();
            while (e.hasMoreElements()) {
                ZipEntry ze = e.nextElement();
                log.debug(dumpZipEntry(ze));
                htSizes.put(ze.getName(), new Integer((int) ze.getSize()));
            }
            zf.close();

            // extract resources and put them into the hashtable.
            FileInputStream fis = new FileInputStream(jarFileName);
            BufferedInputStream bis = new BufferedInputStream(fis);
            ZipInputStream zis = new ZipInputStream(bis);
            ZipEntry ze = null;
            while ((ze = zis.getNextEntry()) != null) {
                if (ze.isDirectory()) {
                    continue;
                }
                log.debug("ze.getName()=" + ze.getName() + "," + "getSize()=" + ze.getSize());
                int size = (int) ze.getSize();
                // -1 means unknown size.
                if (size == -1) {
                    size = htSizes.get(ze.getName()).intValue();
                }
                byte[] b = new byte[(int) size];
                int rb = 0;
                int chunk = 0;
                while (((int) size - rb) > 0) {
                    chunk = zis.read(b, rb, (int) size - rb);
                    if (chunk == -1) {
                        break;
                    }
                    rb += chunk;
                }
                // add to internal resource hashtable
                htJarContents.put(ze.getName(), b);
                log.debug(ze.getName() + "  rb=" + rb + ",size=" + size + ",csize=" + ze.getCompressedSize());
            }
        } catch (NullPointerException e) {
            log.error("NPE while reading jar: " + jarFileName, e);
        } catch (FileNotFoundException e) {
        	log.error("File not found exc. while reading jar: " + jarFileName, e);
        } catch (IOException e) {
        	log.error("I/O exc. while reading jar: " + jarFileName, e);
        }
    }

    /**
     * Dumps a zip entry into a string.
     * 
     * @param ze
     *            a ZipEntry
     */
    private String dumpZipEntry(ZipEntry ze) {
        StringBuffer sb = new StringBuffer();
        if (ze.isDirectory()) {
            sb.append("d ");
        } else {
            sb.append("f ");
        }
        if (ze.getMethod() == ZipEntry.STORED) {
            sb.append("stored   ");
        } else {
            sb.append("deflated ");
        }
        sb.append(ze.getName());
        sb.append("\t");
        sb.append("" + ze.getSize());
        if (ze.getMethod() == ZipEntry.DEFLATED) {
            sb.append("/" + ze.getCompressedSize());
        }
        return (sb.toString());
    }

    
    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        JarResources app = new JarResources("c:\\source\\Jarin\\TEST_DIRECTORY\\jars\\jsp-api-2.0-sources.jar");
        System.out.println(app.getFullJavaNames());
        System.out.println(app.getAllImports("javax.servlet.jsp.PageContext"));
    }

    /**
     * @return file names from extracted jar
     */
    public Set<String> getEntryNames() {
        return htJarContents.keySet();
    }

    /**
     * @param jarResourceName
     * 		name of resource in jar
     * @param source
     * 		wheather to look up java sources or java classes
     * @return
     * 		fully classified java name
     */
    private String getClassNameFromJar(String jarResourceName, boolean source) {
        final String suffix;
        if (source) {
            suffix = ".java";
        } else {
            suffix = ".class";
        }
        if (!jarResourceName.contains(suffix)) {
            return null;
        }
        return jarResourceName.replace(suffix, "").replace("/", ".");
    }

    /**
     * @param javaFullName
     *            "javax.servlet.HttpServlet"
     * @return "javax/servlet/HttpServlet.java"
     */
    private String getJarNameFromJavaName(String javaFullName) {
        return javaFullName.replace(".", "/").concat(".java");
    }

    /**
     * @param set
     * 		set of jar resource names
     * @param source
     * 		wheather jar is source-jar or not
     * @return
     * 		set of fully classified java names
     */
    private HashSet<String> getClassNameFromJar(Set<String> set, boolean source) {
        HashSet<String> result = new HashSet<String>();
        for (String one : set) {
            String brandNew = getClassNameFromJar(one, source);
            if (brandNew != null) {
                result.add(brandNew);
            }
        }
        return result;
    }

    /**
     * @return
     * 		all fully classified class names from class-jar
     */
    public Set<String> getFullClassNames() {
        if(this.jarFileName == null) {
            throw new NullPointerException("Java class can not contain zipped classes as jar do.");
        }
        return getClassNameFromJar(getEntryNames(), false);
    }

    /**
     * @return
     * 		all fully classified class names from source-jar
     */
    public Set<String> getFullJavaNames() {
        if(this.jarFileName == null) {
            throw new NullPointerException("This method can not be called, when JarResource created with contructor C(javaFile) instead of C(jar).");
        }
        return getClassNameFromJar(getEntryNames(), true);
    }

    /**
     * Retrieve all imports from java source file inside jar 
     * @param javaName
     * 		fully classified java name
     * @return
     * 		set of imports
     */
    public Set<String> getAllImports(String javaName) {
        Set<String> result = new HashSet<String>();
        byte[] bytess = getResource(getJarNameFromJavaName(javaName));
        if(bytess==null || bytess.length < 1) {
            return null;
        }
        String[] radky = convertBytes2Strings(bytess);
        for(String line : radky) {
            String importClass = extractFullClassNameFromLine(line);
            if(importClass != null) {
                result.add(importClass);
            }
        }
        return result;
    }
    

    public Set<String> getAllImports() {
        Set<String> result = new HashSet<String>();
        FileUtil util = FileUtil.getInstance();
        String[] radky = util.getFileStrings(this.javaPath);
        for (String line : radky) {
            if (line != null) {
                String importClass = extractFullClassNameFromLine(line);
                if (importClass != null) {
                    result.add(importClass);
                }
            }
        }
        return result;

    }

    private String[] convertBytes2Strings(byte[] bytes) {
        String whole = new String(bytes);
        //byte[] sep = System.getProperty("line.separator").getBytes();
        //String separator =new String(sep);
        return whole.split("\n");
    }

    private String extractFullClassNameFromLine(String line) {
        Pattern pat = Pattern.compile("import .*;");
        Matcher mat = pat.matcher(line);
        String result = null;
        if (mat.find()) {
            result = mat.group(0);
            // bez importu a bez stredniku
            result = result.substring(7, result.length() - 1);
        }
        return result;
    }


    
    /**
     * @return the javaFullName
     */
    public String getJavaFullName() {
        return javaFullName;
    }


    
    /**
     * @return the javaPath
     */
    public String getJavaPath() {
        return javaPath;
    } 

}
